package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.COMPARTMENT_ROLE_BASE;
import static xj.graph2d.DrawShapeConstants.PointInHorizontalCompartment;
import static xj.graph2d.DrawShapeConstants.PointOnHorizontalDivider;
import static xj.graph2d.DrawShapeConstants.cpadx;
import static xj.graph2d.DrawShapeConstants.cpady;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import javax.swing.SwingConstants;

import org.w3c.dom.Element;

import xj.graph2d.BoxGroup;
import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.RectangleShape;
import xj.graph2d.StrokeMap;
import xj.lang.CodeWriter;
import xj.util.gui.Rectangle2DFloat;
import xj.util.xml.XMLUtil;

public class HorizontalCompartmentsAdapter 
  implements HorizontalCompartments, Cloneable {

  public HorizontalCompartmentsAdapter(GraphNodeBase owner) {
    this.owner = owner;
  }

  @Override
  public Object clone() {
    HorizontalCompartmentsAdapter hc = new HorizontalCompartmentsAdapter(owner);
    copyDividers(hc);
    return hc;
  }

  public void copyDividers(HorizontalCompartmentsAdapter hc) {
    if (hc != null && dividers != null) {
      int n = dividers.length;
      float[] divs = new float[n];
      for (int i = 0; i < n; i++) {
	divs[i] = dividers[i];
      }
      hc.setHorizontalDividers(divs);
    }
    hc.lineMode = lineMode;
    hc.lineStyle = lineStyle;
    hc.highlight = highlight;
    hc.expandable = expandable;
    hc.collapsable = collapsable;
    hc.scaleTopCompartment = scaleTopCompartment;
    hc.compartmentNumberFixed = compartmentNumberFixed;
  }

  public void setDividerStyle(DividerLineMode lineMode, int lineStyle) {
    this.lineMode = lineMode;
    this.lineStyle = lineStyle;
  }

  public void setHighLightCompartments(boolean highlight) { 
    this.highlight = highlight;
  }

  public boolean isHighLightCompartments() { 
    return highlight;
  }

  public boolean isExpandable() {
    return expandable;
  }

  public void setExpandable(boolean b) {
    expandable = b;
  }

  public boolean isCollapsable() {
    return collapsable;
  }

  public void setCollapsable(boolean b) {
    collapsable = b;
  }

  public DrawShape getOwner() {
    return owner;
  }

  public void setOwner(GraphNodeBase owner) {
    this.owner = owner;
  }

  public float[] getHorizontalDividers() {
    return dividers;
  }

  public void setHorizontalDividers(float[] dividers) {
    this.dividers = dividers;
  }

  public int getNumberOfHorizontalCompartments() {
    if (dividers != null) {
      return (dividers.length + 1);
    }
    return 1;
  }

  public Rectangle2D getHorizontalCompartmentBounds(int i) {
    if (owner != null) {
      if (dividers == null) { 
	if (i == 0) { 
	  return owner.getBounds();
	}
      } else {
	if (i >= 0 && 
	    i <= dividers.length) { 
	  Point2D p1 = owner.getEnd1();
	  Point2D p2 = owner.getEnd2();
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float h = (float) Math.abs(p1.getY() - p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float ydiv = 0;
	  for (int j = 0; j < i; j++) {
	    ydiv += dividers[j] * h;
	  }
	  float h0;
	  if (i < dividers.length) { 
	    h0 = h * dividers[i];
	  } else {
	    h0 = h - ydiv; 
	  }
	  return new Rectangle2DFloat(x, y + ydiv, w, h0);
	}
      }
      throw new IllegalArgumentException("compartment index: " + i);
    }
    return null;
  }

  public Point2D getHorizontalCompartmentCenter(int i) { 
    if (owner != null) {
      if (dividers == null) { 
	if (i == 0) { 
	  return owner.getCenterPosition();
	}
      } else {
	if (i >= 0 && 
	    i <= dividers.length) { 
	  Point2D p1 = owner.getEnd1();
	  Point2D p2 = owner.getEnd2();
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float h = (float) Math.abs(p1.getY() - p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float ydiv = 0;
	  for (int j = 0; j < i; j++) {
	    ydiv += dividers[j] * h;
	  }
	  float cx = x + w / 2;
	  float cy;
	  if (i < dividers.length) { 
	    cy = y + ydiv + h * dividers[i] / 2;
	  } else { 
	    cy = y + ydiv + (h - ydiv) / 2;
	  }
	  return new Point2D.Float(cx, cy);
	}
      }
      throw new IllegalArgumentException("compartment index: " + i);
    }
    return null;
  }

  public float getHorizontalCompartmentHeight(int i) { 
    if (owner != null) { 
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float h = (float) Math.abs(p1.getY() - p2.getY());
      if (dividers == null) {
	if (i == 0) {
	  return h; 
	}
      } else {
	if (i >= 0 && 
	    i <= dividers.length) { 
	  if (i < dividers.length) { 
	    return h * dividers[i];
	  } else { 
	    float f = 0;
	    for (int j = 0; j < dividers.length; j++) {
	      f += dividers[j];
	    }
	    return h * (1 - f); 
	  }
	}
      }
      throw new IllegalArgumentException("compartment index: " + i);
    }
    return 0;
  }

  // relative to the top of the shape 
  public float getHorizontalCompartmentTop(int i) { 
    if (i == 0) {
      return 0;
    }
    if (dividers != null &&
	owner != null) {
      if (i > 0 && 
	  i <= dividers.length) { 
	Point2D p1 = owner.getEnd1();
	Point2D p2 = owner.getEnd2();
	float h = (float) Math.abs(p1.getY() - p2.getY());
	float ydiv = 0;
	for (int j = 0; j < i; j++) {
	  ydiv += dividers[j] * h;
	}
	return ydiv; 
      }
      throw new IllegalArgumentException("compartment index: " + i);
    }
    return 0;
  }

  public void moveHorizontalDivider(int i, float dy) {
    boolean adjust = true;
    if (owner != null && dividers != null) {
      int n = dividers.length;
      if (i >= 0 && i <= n - 1) {
	Point2D p1 = owner.getEnd1();
	Point2D p2 = owner.getEnd2();
	float y = (float) Math.min(p1.getY(), p2.getY());
	float h = (float) Math.abs(p1.getY() - p2.getY());
	float dy1 = dy / h;
	if ((dividers[i] + dy1) * h >= minCompartmentHeight) {
	  dividers[i] += dy1;
	} else {
	  dy1 = minCompartmentHeight / h - dividers[i];
	  dividers[i] = minCompartmentHeight / h;
	}

	float h1 = 0;
	int k;
	dy1 = dy1 / (n - i + 1); 
	for (k = 0; k < i; k++) {
	  h1 += dividers[k] * h;
	}
	for (; k < n; k++) {
	  if (k > i && adjust) {
	    dividers[k] -= dy1;
	    if (dividers[k] * h < minCompartmentHeight) {
	      dividers[k] = minCompartmentHeight / h;
	      break;
	    }
	  }
	  float hmax = h - (n - k) * minCompartmentHeight;
	  if ((h1 + dividers[k] * h) > hmax) {
	    dividers[k] = (hmax - h1) / h;
	  }
	  h1 += dividers[k] * h;
	}
      }
    }
  }

  public void addHorizontalDivider(int p, float dy) {
    if (dy > 0 && dy < 1) {
      if (dividers != null) {
	int n = dividers.length;
	if (p >= 0 && p <= n) {
	  float f = 1f - dy;
	  float[] newDiv = new float[n + 1];
	  for (int i = 0; i <= n; i++) {
	    if (i < p) {
	      newDiv[i] = dividers[i] * f;
	    } else if (i == p) {
	      newDiv[i] = dy;
	    } else {
	      newDiv[i] = dividers[i - 1] * f;
	    }
	  }
	  dividers = newDiv;
	}
      } else {
	if (p == 0) {
	  dividers = new float[1];
	  dividers[0] = dy;
	}
      }
    } else {
      if (dividers != null) {
	int n = dividers.length;
	if (p >= 0 && p <= n) {
	  float[] newDiv = new float[n + 1];
	  float ydiv = 0;
	  for (int i = 0; i <= n; i++) {
	    if (i < p) {
	      newDiv[i] = dividers[i];
	      ydiv += dividers[i];
	    } else if (i == p) {
	      if (i < n) {
		newDiv[i + 1] = newDiv[i] = dividers[i] / 2;
	      } else {
		newDiv[i] = (1 - ydiv) / 2;
	      }
	      i++;
	    } else {
	      newDiv[i] = dividers[i - 1];
	    }
	  }
	  dividers = newDiv;
	}
      } else {
	if (p == 0) {
	  dividers = new float[1];
	  dividers[0] = 0.5f;
	}
      }
    }
  }

  public void removeHorizontalDivider(int p) {
    if (dividers != null) {
      int n = dividers.length;
      if (p >= 0 && p < n) {
	float[] newDiv = null;
	if (n > 1) {
	  newDiv = new float[n - 1];
	  float dy = dividers[p];
	  float f = 1f / (1f - dy);
	  for (int i = 0; i < n - 1; i++) {
	    if (i < p) {
	      newDiv[i] = dividers[i] * f;
	    } else {
	      newDiv[i] = dividers[i + 1] * f;
	    }
	  }
	}
	dividers = newDiv;
      }
    }
  }

  public void drawHorizontalDividers(Graphics2D g2) {
    if (owner != null && dividers != null) {
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      int gw = 0;
      if (owner instanceof RectangleShape) {
	RectangleShape rect = (RectangleShape) owner;
	if (rect.isShowVerticalGutter()) {
	  gw = rect.getGutterWidth();
	}
      }
      if (w <= 2 * gw) {
	gw = 0;
      }
      drawHorizontalDividers(g2, x + gw, y, w - 2 * gw, h);
    }
  }

  public void drawHorizontalDividers(Graphics2D g2, 
				     float x, float y, float w, float h) {
    if (dividers != null) {
      int n = dividers.length;
      float ydiv = 0;
      if (lineMode == DividerLineMode.Default) {
	for (int i = 0; i < n; i++) {
	  ydiv += dividers[i] * h;
	  if (ydiv > 0 && ydiv < h) {
	    g2.draw(new Line2D.Float(x, y + ydiv, x + w, y + ydiv));
	  }
	}
      } else {
	DrawAttr attr = owner.getDrawAttr();
	Stroke newStroke = null;
	StrokeMap smap = StrokeMap.getInstance();
	if (attr != null) {
	  newStroke = smap.findStroke(attr.getPenSize(), attr.getLineCap(),
				      attr.getLineJoin(), lineStyle);
	} else {
	  newStroke = smap.findStroke(1, BasicStroke.CAP_SQUARE,
				      BasicStroke.JOIN_MITER, lineStyle);
	}
	Stroke oldStroke = null;
	for (int i = 0; i < n; i++) {
	  if (lineMode == DividerLineMode.EveryLine && i == 0 || 
	      lineMode == DividerLineMode.EveryLineExceptFirst && i == 1) {
	    if (newStroke != null) {
	      oldStroke = g2.getStroke();
	      g2.setStroke(newStroke);
	    }
	  }
	  ydiv += dividers[i] * h;
	  if (ydiv > 0 && ydiv < h) {
	    g2.draw(new Line2D.Float(x, y + ydiv, x + w, y + ydiv));
	  }
	}
	if (oldStroke != null && newStroke != null) {
	  g2.setStroke(oldStroke);
	}
      }
    }
  }

  public void drawSubShapes(Graphics2D g2, 
			    int mode, 
			    DrawShape selectedShape,
			    Set<DrawShape> selectionSet,
			    boolean reverseOrder) {
    if (owner != null &&
	dividers != null) {
      List<DrawShape> subshapes = owner.getShapes();
      if (subshapes != null) {
	Rectangle2D oldClipRect = null;
	Rectangle2D newClipRect = owner.getClipRect();
	Shape clip = g2.getClip();
	if (clip != null) { 
	  if (clip instanceof Rectangle2D) { 
	    oldClipRect = (Rectangle2D) clip;
	  } else { 
	    oldClipRect = clip.getBounds2D();
	  }
	}
	if (oldClipRect != null) { 
	  Rectangle2D.intersect(oldClipRect, newClipRect, newClipRect);
	}

	int n = subshapes.size();
	int m = dividers.length;

	if (m >= 1) {
	  Point2D p1 = owner.getEnd1();
	  Point2D p2 = owner.getEnd2();
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float h = (float) Math.abs(p1.getY() - p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float ydiv = 0;
	  float hdiv = 0;
	  for (int i = 0; i <= m; i++) {
	    if (i < m) {
	      hdiv = dividers[i] * h;
	    } else {
	      hdiv = h - ydiv;
	    }
	    DrawShape s = getShapeInCompartment(i);
	    if (s != null && s.isVisible()) {
	      g2.setClip(new Rectangle2DFloat(x, y + ydiv, w, hdiv));
	      s.drawShape(g2, mode, selectedShape, selectionSet);
	    }
	    ydiv += hdiv;
	  }
	}

	int i = (reverseOrder ? n - 1 : 0); 
	while (reverseOrder ? i >= 0 : i < n) {
	  DrawShape s = subshapes.get(i);
	  if (s.isVisible()) {
	    int role = s.getRole();
	    if (role < COMPARTMENT_ROLE_BASE ||
		role > (COMPARTMENT_ROLE_BASE + m)) {
	      if (!owner.isClipSubShapes() || 
		  !owner.isSubShapeClipped(s)) {
		// template shape and nodes on boundary 
		g2.setClip(null);
	      } else {
		g2.setClip(newClipRect); 
	      }
	      s.drawShape(g2, mode, selectedShape, selectionSet);
	    }
	  }
	  if (reverseOrder) {
	    i--;
	  } else {
	    i++;
	  }
	}
	g2.setClip(oldClipRect);	
      }
    }
  }

  public void drawHighLightCompartments(Graphics2D g2, Color bg1,
					float x, float y, float w, float h) {
    if (owner != null && 
	g2 != null) { 
      if (bg1 == null) {
	bg1 = g2.getColor();
      }
      
      if (dividers != null) {       
	float[] clr = bg1.getComponents(null);
	for (int i = 0; i < 3; i++) {
	  clr[i] = (clr[i] * 0.8f);
	}
	Color bg2 = new Color(clr[0], clr[1], clr[2], clr[3]);

	int n = dividers.length;
	float y0 = y; 
	for (int i = 0; i <= n; i++) {
	  float h0;
	  if (i < n) { 
	    h0 = dividers[i] * h;
	  } else { 
	    h0 = (y + h - y0);
	  }
	  if (i % 2 == 0) { 
	    g2.setColor(bg1);
	  } else { 
	    g2.setColor(bg2);
	  }
	  g2.fill(new Rectangle2D.Float(x, y0, w, h0));
	  y0 += h0;
	}
      } else { 
	g2.setColor(bg1);
	g2.fill(new Rectangle2D.Float(x, y, w, h));
      }
    }
  }

  public void drawFrame(Graphics2D g2, float marksize, 
			boolean fill, Color frameColor) {
    if (owner != null && dividers != null) {
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float md = marksize / 2.0f;
      int n = dividers.length;
      float ydiv = 0;

      g2.setColor(frameColor);
      for (int i = 0; i < n; i++) {
	ydiv += dividers[i] * h;
	if (ydiv < h) {
	  Rectangle2D r = new Rectangle2D.Float(x + w / 2 - md, y + ydiv - md,
						marksize, marksize);
	  if (fill) { 
	    g2.fill(r);
	  } else { 
	    g2.draw(r);
	  }
	}
      }
    }
  }

  public int onHorizontalDivider(float px, float py) {
    int result = -1;
    if (owner != null) { 
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      if (px >= x - d && 
	  px <= x + w + d && 
	  py >= y - d && 
	  py <= y + h + d) {
	if (dividers != null) {
	  int n = dividers.length;
	  float ydiv = 0;
	  for (int i = 0; i < n; i++) {
	    ydiv += dividers[i] * h;
	    float dy = Math.abs(y + ydiv - py);
	    if (dy <= d) {
	      return PointOnHorizontalDivider + i;
	    } else if (py < y + ydiv) {
	      return PointInHorizontalCompartment + i;
	    }
	  }
	  result = PointInHorizontalCompartment + n;
	} else {
	  result = PointInHorizontalCompartment;
	}
      }
    }
    return result;
  }

  public int inHorizontalCompartment(DrawShape s) {
    if (s != null && owner != null) {
      Point2D c = s.getCenterPosition();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      if (cx >= x - d && 
	  cx <= x + w + d && 
	  cy >= y - d && 
	  cy <= y + h + d) {
	if (dividers != null) {
	  int n = dividers.length;
	  float ydiv = 0;
	  for (int i = 0; i < n; i++) {
	    ydiv += dividers[i] * h;
	    if (cy < y + ydiv) {
	      return i;
	    }
	  }
	  return n;
	} else {
	  return 0;
	}
      }
    }
    return -1;
  }

  public boolean isNumberOfHorizontalCompartmentsFixed() {
    return compartmentNumberFixed;
  }

  public void setNumberOfHorizontalCompartmentsFixed(boolean b) {
    compartmentNumberFixed = b;
  }

  public boolean isScaleTopCompartment() {
    return scaleTopCompartment;
  }

  public void setScaleTopCompartment(boolean scaleTopCompartment) {
    this.scaleTopCompartment = scaleTopCompartment;
  }

  public void adjustHorizontalDividers(float dy, boolean top) {
    if (dy != 0 && 
	dividers != null) {
      int n = dividers.length;
      if (n > 0 && 
	  !scaleTopCompartment) {
	Point2D p1 = owner.getEnd1();
	Point2D p2 = owner.getEnd2();
	float y = (float) Math.min(p1.getY(), p2.getY());
	float h = (float) Math.abs(p1.getY() - p2.getY());
	float div1 = dividers[0];
	if (top) {
	  div1 += div1 * dy / h;
	} else {
	  div1 -= div1 * dy / h;
	}
	float h1 = div1 * h;
	if (h - h1 >= n * minCompartmentHeight) {
	  dividers[0] = div1;
	} else {
	  dividers[0] = (h - n * minCompartmentHeight) / h;
	}
      }
    }
  }

  // no adjustment of dividers 
  protected void positionCompartmentContent(int i) {
    if (owner != null) { 
      float[] div = getHorizontalDividers();
      if (div != null) {
	int n = div.length;
	if (n > 0 && 
	    i >= 0 && 
	    i <= n) {
	  DrawShape shape = getShapeInCompartment(i);
	  if (shape != null) {
	    positionShapeInCompartment(shape, i);
	  }
	}
      }
    }
  }

  // no adjustment of dividers 
  protected void positionShapeInCompartment(DrawShape shape, int i) {
    if (shape != null &&
	owner != null) { 
      float[] div = getHorizontalDividers();
      if (div != null) {
	int n = div.length;
	if (n > 0 && 
	    i >= 0 && 
	    i <= n) {
	  Point2D p1 = owner.getEnd1();
	  Point2D p2 = owner.getEnd2();
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float h = (float) Math.abs(p1.getY() - p2.getY());
	  float h0 = 0;
	  float h1 = h;
	  for (int j = 0; j < i; j++) {
	    h0 += div[j] * h;
	  }
	  if (i < n) {
	    h1 = h0 + div[i] * h;
	  }
	  if (owner instanceof RectangleShape) { 
	    RectangleShape rect = (RectangleShape) owner;
	    int gw = 0;
	    if (rect.isShowVerticalGutter()) {
	      gw = rect.getGutterWidth();
	      x += gw;
	      w -= 2 * gw;
	    }
	  }
	  
	  float dx = 0, dy = 0;
	  if (w > cpadx * 2) {
	    dx = cpadx;
	    x += dx;
	    w -= dx * 2;
	  }
	  float cy = y + h0;
	  float ch = h1 - h0;
	  if (ch > cpady * 2) {
	    dy = cpady;
	    cy += dy;
	    ch -= dy * 2;
	  }
	  if (shape instanceof BoxGroup) {
	    BoxGroup box = (BoxGroup) shape;
	    box.doLayout();
	    owner.positionShape(shape, x, cy, w, ch, 
				SwingConstants.TOP, SwingConstants.LEFT);
	  } else {
	    shape.setEnds(x, cy, x + w, cy + ch);
	    shape.doLayout();
	    //positionShape(shape, x, cy, w, ch);
	  }
	}
      }
    }
  }

  // adjust dividers 
  public void fitCompartments() {
    fitCompartments(true, 0f, 0f); 
  }

  public void fitCompartments(boolean keepWidth) {
    fitCompartments(keepWidth, 0f, 0f); 
  }

  // adjust dividers 
  public void fitCompartments(boolean keepWidth, float xadj, float yadj) {
    if (owner != null) { 
      int n = getNumberOfHorizontalCompartments();
      float hh = owner.getShapeHeight();
      float ww = owner.getShapeWidth();
      float h = hh, w = ww;

      DrawShape s0 = getShapeInCompartment(0);
      BoxLabel header = (BoxLabel) owner.getLabelShape(DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
      if (s0 == null) { 
	s0 = header; 
      }

      if (n == 1) {
	if (s0 != null) { 
	  h = s0.getShapeHeight() + 2 * cpady;
	  w = s0.getShapeWidth() + 2 * cpady;
	}
	h += yadj;      
      } else if (n > 1) {  
	float[] dividers;
	float[] cheight = new float[n];
	float[] cwidth = new float[n];
	h = 0;
	w = 0;
	for (int i = 0; i < n; i++) { 
	  DrawShape s = getShapeInCompartment(i);
	  if (s == null && i == 0) {
	    s = header; 
	  }
	  if (s != null) { 
	    cheight[i] = s.getShapeHeight() + 2 * cpady;
	    cwidth[i] = s.getShapeWidth() + 2 * cpady;
	  } else { 
	    cheight[i] = 2 * cpady;
	    cwidth[i] =  2 * cpady;
	  }
	  if (i == 0) { 
	    cheight[i] += yadj;
	  }

	  h += cheight[i];
	  w = Math.max(w, cwidth[i]);
	}

	dividers = new float[n - 1];
	for (int i = 0; i < n - 1; i++) { 
	  dividers[i] = cheight[i] / h;
	}
	setHorizontalDividers(dividers);
      }
    
      if (owner instanceof RectangleShape) {
	RectangleShape rect = (RectangleShape) owner;
	if (rect.isShowVerticalGutter()) {
	  w += rect.getGutterWidth() * 2;
	}
      }

      if (xadj > 0 && 
	  w < (ww + xadj)) { 
	w = ww + xadj;
      }
      float dw = w - ww;
      float dh = h - hh;
      if (keepWidth && dw < 0) { 
	dw = 0;
      }
      owner.moveEnd2(dw, dh);
      owner.doLayout();
      //owner.positionSubNodes();
    }
  }

  public DrawShape getShapeInCompartment(int i) {
    if (owner != null) { 
      int role = COMPARTMENT_ROLE_BASE + i;
      DrawShape shape = owner.findInRole(role);
      return shape;
    } 
    return null;
  }

  public void writeXML(CodeWriter out, String namespacePrefix) {
    if (out != null) {
      String divname = DrawShapeBase.getPrefixedName("Dividers",
	  namespacePrefix);
      if (dividers != null) {
	int n = dividers.length;
	out.print("<" + divname + ' ' + 
		  getXMLAttributeString("ndiv", n) + ' ' + 
		  getXMLAttributeString("fixed", compartmentNumberFixed) + ' ' + 
		  getXMLAttributeString("scaleTop", scaleTopCompartment) + ' ' + 
		  getXMLAttributeString("mode", lineMode) + ' ' + 
		  getXMLAttributeString("highlight", highlight) + ' ' + 
		  getXMLAttributeString("expandable", expandable) + ' ' + 
		  getXMLAttributeString("collapsable", collapsable) + ' ' + 
		  getXMLAttributeString("style", lineStyle) + ">");
	for (int i = 0; i < n; i++) {
	  if (i > 0) {
	    out.print(" ");
	  }
	  out.print(Float.toString(dividers[i]));
	}
	out.println("</" + divname + ">");
      } else {
	out.println("<" + divname + ' ' + getXMLAttributeString("ndiv", 0) + "/>");
      }
    }
  }

  public void setAttrFromXML(Element node, String namespaceURI) {
    if (node != null) {
      String uri = node.getNamespaceURI();
      if (namespaceURI == null || namespaceURI.equals(uri)) {
	String s = node.getAttribute("ndiv");
	int ndiv = 0;
	try {
	  ndiv = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	  ndiv = 0;
	}

	if (ndiv > 0) {
	  dividers = new float[ndiv];

	  String val = XMLUtil.getTextChildNodeValue(node);
	  StringTokenizer st = new StringTokenizer(val, " ");
	  for (int j = 0; j < ndiv && st.hasMoreTokens(); j++) {
	    String fval = st.nextToken();
	    if (fval != null && fval.length() > 0) {
	      try {
		dividers[j] = Float.parseFloat(fval);
	      } catch (NumberFormatException ex) {
	      }
	    }
	  }
	} else {
	  dividers = null;
	}

	s = node.getAttribute("fixed");
	try {
	  compartmentNumberFixed = Boolean.valueOf(s);
	} catch (IllegalArgumentException ex) {
	}

	s = node.getAttribute("scaleTop");
	try {
	  scaleTopCompartment = Boolean.valueOf(s);
	} catch (IllegalArgumentException ex) {
	}

	s = node.getAttribute("mode");
	try {
	  lineMode = Enum.valueOf(DividerLineMode.class, s);
	} catch (IllegalArgumentException ex) {
	}

	s = node.getAttribute("style");
	try {
	  lineStyle = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("highlight ");
	try {
	  highlight = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("expandable");
	try {
	  expandable = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("collapsable");
	try {
	  collapsable = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}
      }
    }
  }

  protected GraphNodeBase owner;

  protected float[] dividers;

  /*
   * n = dividers.length.
   * height is the hieght of the rectangle. 
   * There are n + 1 compartments inside the rectangle. 
   * 0 < dividers[i] < 1; (the fraction of the height) 
   * Sum dividers[0 .. n-1] < 1. 
   * The height of the i-th compartment is dividers[i] * height.
   * The height of the (n+1)-th compartment is (Sum dividers[0 .. n-1]) * height.
   */

  protected DividerLineMode lineMode;

  protected int lineStyle;

  protected boolean highlight = false;

  protected boolean expandable = true;

  protected boolean collapsable = true;

  protected float minCompartmentHeight = 4;

  protected boolean compartmentNumberFixed = false;

  protected boolean scaleTopCompartment = true;

}
